#include "message_passing.h"

/* builds a message and returns it */
void build_message(int32_t from, int32_t to, int32_t data, int32_t priority, int8_t data_flag, message* mess){
  mess->from = from;
  mess->to = to;
  if(data_flag == INT_DATA)
    mess->data.int_data = data;
  else
    strncpy(mess->data.str_data,(char*)data, MAX_LENGTH_DATA);
  mess->data_flag = data_flag;
  mess->priority = priority;
}

/* sends a message with data in it to the process identified by pid and returns 1 if the operation has been done successfully, 0 otherwise */ 
int32_t send_integer(int32_t to, int32_t data, int32_t priority ){
  message mess;
  build_message(pcb_get_pid(get_running()),to,data,priority,INT_DATA,&mess);
  return send(to, mess);
}

/* sends a message with data in it to the process identified by pid and returns 1 if the operation has been done successfully, 0 otherwise */ 
int32_t send_string(int32_t to, char* data,int32_t priority ){
  message mess;
  build_message(pcb_get_pid(get_running()),to,(int32_t)data,priority,STR_DATA,&mess);
  return send(to,mess);
}

/* sends the message mess to the process to */
int32_t send(int32_t to, message mess){
  PCB* pcb = get_process_by_pid(to);
  if(pcb != NULL){
    return send_pcb(pcb, mess);
  }
  return 0;
}

/* sends the message mess to pcb */
int32_t send_pcb(PCB* pcb, message mess){
  if(pcb == NULL)
    return 0;
  message_queue* m_queue =pcb_get_message_queue(pcb);
  //we build the message and insert it
  if(insert_message(m_queue, mess)) {
    if(wake_me_up(m_queue, &mess)){
      //the new message has to wake the receiver up
      wake_up_pcb(pcb);
      //reset the to's message queue status
      m_queue->status = NOT_WAITING;
    }
    return 1;
  }
  //the insertion failed
  return 0;
}


/* returns the first message and removes it from the queue. Blocking call if the queue is empty. */
int32_t receive_first(message* mess){
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(get_running());
  if(!get_message_first(m_queue, mess)){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_FIRST,0);
    if(!block_pcb(get_running()))
      return -1; //fatal error
    return 0; //message not found at this time
  }
  return 1;
}

/* returns the first message sent by from and removes it from the queue. Blocking call if there is no such message */
int32_t receive_from(int32_t from, message* mess){
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(get_running());
  if(!get_message_from(m_queue, from, mess)){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_FROM, from);
    if(!block_pcb(get_running()))
      return -1;
    return 0;
  }
  return 1;
}

/* returns the first message with the correct priority and removes it from the queue. Blocking call if there no such message */
int32_t receive_priority(int32_t priority,message* mess){
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(get_running());
  if(!get_message_priority(m_queue, priority, mess)){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_PRIORITY, priority);
    if(!block_pcb(get_running()))
      return -1;
    return 0;
  }
  return 1;
}

/* gets the first message. If none is available, waits at most timeout ms before returning. It also returns as soon as a message arrives. Returns 1 if a message has arrived, 0 otherwise and -1 in case of fatal error.*/
int32_t receive_first_timeout(message* mess, uint32_t timeout){
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(get_running());
  if(!get_message_first(m_queue, mess)){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_FIRST,0);
    if(timeout) //when timeout is 0, it indicates the second check. No delay there.
      if(!delay_pcb(get_running(),timeout))
	return -1; //fatal error
    //if timeout is not 0, indicates that the process is delayed. Otherwise, indicates that no correct message has arrived during the specified time
    return 0; 
  }
  return 1;
}

/*  gets the first message from from. It none is available, waits at most timeout ms before returning. It returns when a correct message arrives or at the end of the given time, whichever comes first. Returns 1 if a message has arrived or 0 if the caller is delayed. */
int32_t receive_from_timeout(int32_t from, message* mess, uint32_t timeout){
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(get_running());
  if(!get_message_from(m_queue, from, mess)){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_FROM, from);
    if(timeout)
      if(!delay_pcb(get_running(),timeout))
	return -1;
    return 0;
  }
  return 1;
}

/*  gets the first message with the correct priority. It none is available, waits at most timeout ms before returning. It returns when a correct message arrives or at the end of the given time, whichever comes first. Returns 1 if a message has arrived or 0 if the caller is delayed. */
int32_t receive_priority_timeout(int32_t priority, message* mess, uint32_t timeout){
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(get_running());
  if(!get_message_priority(m_queue, priority, mess)){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_PRIORITY, priority);
    if(timeout)
      if(!delay_pcb(get_running(),timeout))
	return -1;
    return 0;
  }
  return 1;
}

/* returns the number of messags in the queue of the PCB pcb */
int32_t get_number_messages(PCB* pcb){
  return message_queue_get_count(pcb_get_message_queue(pcb));
}

